﻿using System;
using System.Collections.Generic;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Zhy.Communication
{
    public class SerialPortCommunication
    {
        public void SerialPortTest() 
        {
            // 这是个静态方法，获取程序运行环境下的所有串口名称，供选择打开连接
            // 可以用于串口下拉列表选择
            string[] portNames = SerialPort.GetPortNames();

            // 默认使用过程
            SerialPort serialPort = new SerialPort();

            #region 串口连接属性
            // 作用：打开串口，基于打开的串口进行收发数据
            // 指定打开的串口
            serialPort.PortName = "COM1";
            //serialPort.PortName = "COM10";// 不存在的串口名称
            // 波特率：每秒钟传输的位数量   101001010  101010110010101001
            // 以设备为准
            serialPort.BaudRate = 9600;
            // 数据位：以多少个位为一个数据
            serialPort.DataBits = 8;
            // 校验位：针对传输数据被干扰时的对应，奇   偶
            serialPort.Parity = Parity.None;
            // 停止位：一个
            serialPort.StopBits = StopBits.One;
            #endregion

            #region 串口其它属性设置
            // 发送缓冲区大小 - 用于解决在线路不通时持续发送数据缓冲区爆掉问题。
            // 默认值是4096，设置小于4096的数字是无效
            //serialPort.WriteBufferSize = 5100;

            // 接收缓冲区大小
            //serialPort.ReadBufferSize = 5100;

            // 设置接收超时:2秒钟超时，调用Read方法的时候
            // 可以用来判断通信状态。但是这里有区分：接收不确定。应答模式（心跳）
            // 在数据发送后就要接收到返回信息时可以使用 - 场景比较多还是很实用的
            // serialPort.ReadTimeout = 10000;// 这个时间

            // 设置按行接收数据的时候，以什么标识符判断行结束
            //serialPort.NewLine = ";";

            #endregion

            #region 串口事件
            // 以什么时机进行DataReceived事件的触发，串口缓冲区的字节数
            // 10：当串口接收到指定个字节就触发事件
            // 这里不是说每接收到一个字节就触发一次
            serialPort.ReceivedBytesThreshold = 1;
            // 接收数据事件
            serialPort.DataReceived += (se, ev) =>  // 接收数据触发事件
            {   // se是串口对象
                // 这里的逻辑是被动触发
                //string str = serialPort.ReadExisting(); // 一次性读取所有数据

                byte[] bytes = new byte[serialPort.BytesToRead]; // 获取动态的接收数组
                serialPort.Read(bytes, 0, bytes.Length); // 读取接收到的数据
            };

            serialPort.ErrorReceived += (se, ev) => // 通讯异常触发事件
            {
                if (ev.EventType == SerialError.RXParity)  // 异常类型校验 
                {
                    // 001101011   01010101010  
                }
            };
            #endregion

            #region 打开连接
            // 打开方法
            bool state = serialPort.IsOpen;
            serialPort.Open();
            state = serialPort.IsOpen;
            #endregion


            #region 关于打开串口时可能出现的异常
            // 打开时，可能出现异常情况：
            // 1、设置了不存在的串口名称
            //    异常：System.IO.FileNotFoundException:“Could not find file 'COM10'.”

            // 2、重复打开串口
            //    异常：System.InvalidOperationException:“The port is already open.”

            // 3、串口独占：串口已经被其他程序打开过
            //    异常：System.UnauthorizedAccessException:“Access to the path 'COM1' is denied.”
            #endregion




            #region 关于发送的方法调用
            // 发送
            //** public void Write(byte[] buffer, int offset, int count)
            //public void Write(char[] buffer, int offset, int count)
            //public void Write(string text)
            #region 发送数据方法
            //byte[] buffer = new byte[] { 1, 2, 3, 255 };   //具体要发送的数据
            //byte[] buffer = new byte[] { 0x01, 0x02, 0x03, 0xFF };
            //string str = "Hello world";
            // 将要发送的数据转码成byte数组
            //byte[] buffer1 = Encoding.ASCII.GetBytes(str);// 没有中文的转码方式
            ////byte[] buffer = Encoding.UTF8.GetBytes(str);// 如果设备需要的字符串值编码的支持
            //serialPort.Write(buffer1, 0, buffer1.Length);// 这个数据实际是写入到端口的
            #endregion

            #region 缓冲区设置的测试
            // 关于发送缓冲区大小的作用 - 测试缓冲区的一段代码
            //byte[] buffer = new byte[5100];
            //buffer[0] = 0x01;
            //buffer[4095] = 0x02;
            //buffer[5099] = 0x03;
            //serialPort.Write(buffer, 0, buffer.Length);
            #endregion

            #region 设置指定结束符的发送方法
            // 在发送的文本后面加了换行符\LF
            // 可以用于按行接收数据的时候，以什么符号作为判断结束-具体符号有NewLine属性设置
            //serialPort.WriteLine("Hello"); // \r\n
            #endregion
            #endregion

            #region 关于接收的方法调用
            // 接收
            // ** public int Read(byte[] buffer, int offset, int count)
            // public int Read(char[] buffer, int offset, int count)
            #region 卡线程读取方法
            // 这种接收方式会卡线程-会一直等待。
            //byte[] bytes = new byte[1024]; // 每次在缓冲区中读取指定的字节数
            //serialPort.Read(bytes, 0, bytes.Length);// 卡线程
            #endregion

            #region 不卡线程读取方法
            // 这种接收方式不卡线程-但是有可能会接收不到数据
            //byte[] bytes = new byte[serialPort.BytesToRead]; // 设置动态的接收数组
            //serialPort.Read(bytes, 0, bytes.Length);// 不卡线程 
            #endregion

            #region 设置指定结束符的读取方法
            // 这个方法，只有在接收到\LF这个数据的时候，才算接收结束
            // 可以配合NewLine属性进行 行结束符的判断
            //string value = serialPort.ReadLine();
            #endregion

            #region 指定接收字符的读取方法
            // 将接收一个字符串，只有接收到特定字符的时候，判断是接收完成
            //string value = serialPort.ReadTo(";");  // 指定";"作为接收完成的字符
            #endregion

            #region 一次性将缓冲区的所有数据全部读取出来的读取方法
            // 一般的接收Read有什么区别？
            // 区别：Read的时候是要指定字节数（每读10个字节，缓冲区就移除10个字节）
            //       这个方法不需要，当于是清空缓冲区
            //string value = serialPort.ReadExisting();  // 不卡线程
            #endregion

            #region 一次只读取一个字节的读取方法
            // 按照字节进行数据接收，对方一共发了3个字，数据先发到Read缓冲
            //int value = serialPort.ReadByte();//
            //serialPort.DiscardInBuffer();// 清除缓冲区，导致接下来没有数据
            //value = serialPort.ReadByte();
            //value = serialPort.ReadByte();
            #endregion
            #endregion

            #region 关闭连接
            //// 程序退出后串口连接自动关闭
            //serialPort.Close();

            //// 短连接通信
            #endregion

            #region 解决串口同时读写问题
            //// 关于读写问题
            //Task.Run(async () =>
            //{
            //    while (true)
            //    {
            //        byte[] buffer = new byte[] {
            //            0x01,0x03,0x00 ,0x00 ,0x00 ,0x0A ,0xC5 ,0xCD
            //        };
            //        commads.Add(buffer);// 

            //        //Send(buffer, 25, serialPort);

            //        await Task.Delay(200);
            //    }
            //});

            //// 写入
            //byte[] datas = new byte[] {
            //    0x01 ,0x06,0x00 ,0x00 ,0x00 ,0x7B ,0xC9 ,0xE9
            //};
            ////serialPort.Write(datas, 0, datas.Length);
            ////Send(datas, 8, serialPort);

            //commads.Insert(0, datas);
            #endregion
        }

        #region 基于锁进行逻辑互斥
        /// <summary>
        /// 使用锁的方式解决同时发送和读取的问题。
        /// </summary>
        private static readonly object _lock = new object();
        /// <summary>
        /// 数据发送方法
        /// </summary>
        /// <param name="bytes">要发送的数据</param>
        /// <param name="len">接收数据长度</param>
        /// <param name="serialPort">串口对象</param>
        static void Send(byte[] bytes, int len, SerialPort serialPort)
        {
            // 里面的逻辑，一个时刻只能有一个执行对象
            lock (_lock)
            {
                serialPort.Write(bytes, 0, bytes.Length); // 发送数据

                byte[] buffer = new byte[len];
                serialPort.Read(buffer, 0, buffer.Length); // 读取数据
            }
        }
        #endregion

        #region 基于队列进行逻辑互斥
        /// <summary>
        /// 所有要发送的数据
        /// </summary>
        static List<byte[]> commads = new List<byte[]>();

        /// <summary>
        /// 采用队列的方式发送数据
        /// </summary>
        /// <param name="serialPort">串口对象</param>
        static void Read(SerialPort serialPort)
        {
            while (true)
            {
                if (commads.Count == 0) continue;

                byte[] datas = commads[0];
                serialPort.Write(datas, 0, datas.Length); // 发送数据


                ///*****
                ///

                commads.RemoveAt(0); // 队列中发送数据移除
            }
        }
        #endregion
    }
}
